﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data.SqlClient;
using System.Data;
using BEELab.Data;
using BEELab.Logic.ProgramManagement.SessionManagement;
using BEELab.Logic.ProgramManagement.VariableManagement;
using BEELab.Logic.ProgramManagement.StageManagement;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.AspNet.SignalR;
using Microsoft.ClearScript;
using Microsoft.ClearScript.V8;
using System.IO;
using System.Threading;

namespace BEELab.Logic.Execution
{
    public partial class Execute : System.Web.UI.Page
    {
        private Dictionary<string, VariableManagement.Variable> SubjectVariables;

        /// <summary>
        /// Occures when the user is activating a Session.
        /// Gets all variables values from DB and updates values on Session
        /// </summary>
        /// <param name="SessionID">Session ID</param>
        /// <returns>True if secceeded, False otherwise</returns>
        public static Boolean EstablishSession(int SessionID)
        {
            //Retrieve parent program ID
            int ProgramID = SessionManagement.GetProgramIDBySessionID(SessionID);
            if (ProgramID == 0)
                return false;

            //Gets all variables IDs that related to this session's parent program 
            List<int> VariableIDs = VariableManagement.ListByProgramID(ProgramID);

            Global.VariableScopeDictionary[SessionID] = new Dictionary<string, string>();
            Global.SessionVariables[SessionID] = new Dictionary<string, VariableManagement.Variable>();
            Global.VariableIDNameDictionary[SessionID] = new Dictionary<int, string>();

            //Gets all information of variables that related to parent program 
            SqlCommand command = new SqlCommand();
            command.CommandText = "SELECT * FROM Variables WHERE ProgramID=@ProgramID";
            command.Parameters.Add(new SqlParameter("@ProgramID", ProgramID));
            DataTable dt = DBHelper.ExecuteQuery(command);

            if (dt == null)
                return false;

            //Gets all variables IDs and values that related to parent program 
            command = new SqlCommand();
            command.CommandText = "SELECT VariableID, Value FROM InitialValues WHERE Type='Session' AND ParentID=@SessionID";
            command.Parameters.Add(new SqlParameter("@SessionID", SessionID));
            DataTable initialValues = DBHelper.ExecuteQuery(command);

            string initialValue;
            DataRow[] dr;
            foreach (DataRow row in dt.Rows)
            {
                Global.VariableIDNameDictionary[SessionID][int.Parse(row["ID"].ToString())] = row["Name"].ToString();
                Global.VariableScopeDictionary[SessionID][row["Name"].ToString()] = row["Scope"].ToString();
                if (row["Scope"].ToString().Equals("Session"))
                {
                    dr = initialValues.Select("VariableID=" + row["ID"].ToString());
                    if (dr.Length > 0)
                    {
                        initialValue = dr[0]["Value"].ToString();
                    }
                    else
                    {
                        initialValue = row["DefaultValue"].ToString();
                    }

                    VariableManagement.Variable variable = new VariableManagement.Variable(row["Name"].ToString(), row["Description"].ToString(), row["Scope"].ToString(), initialValue);
                    variable.SessionID = SessionID;
                    variable.SubjectID = "0";
                    Global.SessionVariables[SessionID][variable.Name] = variable;
                }
            }

            Global.SessionVariables[SessionID][Properties.Settings.Default.SubjectCount] = new VariableManagement.Variable(Properties.Settings.Default.SubjectCount, "Subjects Counter", "Session", "0");
            Global.SessionVariables[SessionID][Properties.Settings.Default.SubjectCount].SubjectID = "0";
            Global.SessionVariables[SessionID][Properties.Settings.Default.SubjectCount].SessionID = SessionID;
            Global.VariableScopeDictionary[SessionID][Properties.Settings.Default.SubjectCount] = "Session";

            Global.SessionVariables[SessionID][Properties.Settings.Default.SubjectList] = new VariableManagement.Variable(Properties.Settings.Default.SubjectList, "Subjects Counter", "Session", "[]");
            Global.SessionVariables[SessionID][Properties.Settings.Default.SubjectList].SubjectID = "0";
            Global.SessionVariables[SessionID][Properties.Settings.Default.SubjectList].SessionID = SessionID;
            Global.VariableScopeDictionary[SessionID][Properties.Settings.Default.SubjectList] = "Session";

            PreloadData(SessionID, ProgramID);

            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="SessionID">Session ID</param>
        /// <param name="ProgramID">Program ID</param>
        public static void PreloadData(int SessionID, int ProgramID)
        {
            if (!Global.BlockElementsDictionary.ContainsKey(SessionID))
            {
                Global.BlockElementsDictionary[SessionID] = new Dictionary<int, BlockElements.BlockElement>();

                SqlCommand command = new SqlCommand();
                command.CommandText = "SELECT * FROM BlockElements WHERE StageID IN (SELECT ID FROM Stages WHERE ProgramID=@ProgramID)";
                command.Parameters.Add(new SqlParameter("@ProgramID", ProgramID));
                DataTable dt = DBHelper.ExecuteQuery(command);

                int id;
                foreach (DataRow row in dt.Rows)
                {
                    id = int.Parse(row["ID"].ToString());
                    Global.BlockElementsDictionary[SessionID][id] = new BlockElements.BlockElement(id, dt);
                }
            }

            if (!Global.PresentationElementsDictionary.ContainsKey(SessionID))
            {
                Global.PresentationElementsDictionary[SessionID] = new Dictionary<int, PresentationElements.PresentationElement>();

                SqlCommand command = new SqlCommand();
                command.CommandText = "SELECT * FROM PresentationElements WHERE StageID IN (SELECT ID FROM Stages WHERE ProgramID=@ProgramID)";
                command.Parameters.Add(new SqlParameter("@ProgramID", ProgramID));
                DataTable dt = DBHelper.ExecuteQuery(command);

                int id;
                foreach (DataRow row in dt.Rows)
                {
                    id = int.Parse(row["ID"].ToString());
                    Global.PresentationElementsDictionary[SessionID][id] = new PresentationElements.PresentationElement(id, dt);
                }
            }

            if (!Global.DataElementsDictionary.ContainsKey(SessionID))
            {
                Global.DataElementsDictionary[SessionID] = new Dictionary<int, DataElements.DataElement>();

                SqlCommand command = new SqlCommand();
                command.CommandText = "SELECT * FROM DataElements WHERE StageID IN (SELECT ID FROM Stages WHERE ProgramID=@ProgramID)";
                command.Parameters.Add(new SqlParameter("@ProgramID", ProgramID));
                DataTable dt = DBHelper.ExecuteQuery(command);

                int id;
                foreach (DataRow row in dt.Rows)
                {
                    id = int.Parse(row["ID"].ToString());
                    Global.DataElementsDictionary[SessionID][id] = new DataElements.DataElement(id, dt);
                }
            }
        }

        /// <summary>
        /// Occures when a subject joins a new session. 
        /// Creates new subject along with it's relevant session and invitation IDs and inserts to DB.
        /// </summary>
        /// <param name="SessionID">Session ID</param>
        /// <param name="InvitationID">Invitation ID</param>
        /// <returns>Subject ID</returns>
        public static string CreateSubject(int SessionID, int InvitationID)
        {
            string SubjectID = CryptographyHelper.GenerateRandomString(32);

            SqlCommand command = new SqlCommand();
            command.CommandText = "INSERT INTO Subjects (ID, SessionID, InvitationID) VALUES (@ID, @SessionID, @InvitationID); SELECT SCOPE_IDENTITY();";
            command.Parameters.Add(new SqlParameter("@ID", SubjectID));
            command.Parameters.Add(new SqlParameter("@SessionID", SessionID));
            command.Parameters.Add(new SqlParameter("@InvitationID", InvitationID));

            int res = DBHelper.ExecuteNonQuery(command);

            if (res == 1)
                return SubjectID;

            Global.MessageBoxMutex[SubjectID] = new Mutex(false);

            return null;
        }

        /// <summary>
        /// Occures once new invitation for a session is created. Insert invitation details into DB
        /// </summary>
        /// <param name="InvitationID">Invitation ID</param>
        /// <returns>Subject ID</returns>
        public string JoinSession(int InvitationID)
        {
            int SessionID = InvitationManagement.GetSessionIDByInvitationID(InvitationID);
            if (SessionID == 0)
                return null;

            if (Global.SessionVariables.ContainsKey(SessionID) == false)
            {
                //EstablishSession(SessionID);
                return null;
            }

            int ProgramID = SessionManagement.GetProgramIDBySessionID(SessionID);
            if (ProgramID == 0)
                return null;

            //Inserts new line to DB containg subjectID and its relevant session and invitation
            string SubjectID = "\"" + CreateSubject(SessionID, InvitationID) + "\"";

            Dictionary<string, VariableManagement.Variable> SubjectVariables = new Dictionary<string, VariableManagement.Variable>();

            //Gets all program's variables
            SqlCommand command = new SqlCommand();
            command.CommandText = "SELECT * FROM Variables WHERE ProgramID=@ProgramID AND Scope='Subject'";
            command.Parameters.Add(new SqlParameter("@ProgramID", ProgramID));
            DataTable dt = DBHelper.ExecuteQuery(command);

            if (dt == null)
                return null;

            //Gets all relevant session variables
            command = new SqlCommand();
            command.CommandText = "SELECT VariableID, Value, Type FROM InitialValues WHERE (Type='Session' AND ParentID=@SessionID) OR (Type='Invitation' AND ParentID=@InvitationID)";
            command.Parameters.Add(new SqlParameter("@SessionID", SessionID));
            command.Parameters.Add(new SqlParameter("@InvitationID", InvitationID));
            DataTable initialValues = DBHelper.ExecuteQuery(command);

            string initialValue;
            DataRow[] dr;

            //Fill in default values to variables on this specific invitation
            foreach (DataRow row in dt.Rows)
            {
                dr = initialValues.Select("VariableID=" + row["ID"].ToString() + " AND Type='Invitation'");
                if (dr.Length > 0)
                {
                    initialValue = dr[0]["Value"].ToString();
                }
                else
                {
                    dr = initialValues.Select("VariableID=" + row["ID"].ToString() + " AND Type='Session'");
                    if (dr.Length > 0)
                    {
                        initialValue = dr[0]["Value"].ToString();
                    }
                    else
                    {
                        initialValue = row["DefaultValue"].ToString();
                    }
                }

                VariableManagement.Variable variable = new VariableManagement.Variable(row["Name"].ToString(), row["Description"].ToString(), row["Scope"].ToString(), initialValue);
                SubjectVariables[variable.Name] = variable;
                SubjectVariables[variable.Name].SessionID = SessionID;
                SubjectVariables[variable.Name].SubjectID = SubjectID.Replace("\"", "");
            }

            SubjectVariables[Properties.Settings.Default.SubjectID] = new VariableManagement.Variable(Properties.Settings.Default.SubjectID, "Subject ID", "Subject", SubjectID);
            SubjectVariables[Properties.Settings.Default.SubjectID].SubjectID = SubjectID.Replace("\"", "");
            SubjectVariables[Properties.Settings.Default.SubjectID].SessionID = SessionID;
            Global.VariableScopeDictionary[SessionID][Properties.Settings.Default.SubjectID] = "Subject";

            SubjectVariables[Properties.Settings.Default.NextStage] = new VariableManagement.Variable(Properties.Settings.Default.NextStage, "Indicate the next stage to load", "Subject", "\"" + InvitationManagement.GetFirstStage(InvitationID) + "\"");
            SubjectVariables[Properties.Settings.Default.NextStage].SubjectID = SubjectID.Replace("\"", "");
            SubjectVariables[Properties.Settings.Default.NextStage].SessionID = SessionID;
            Global.VariableScopeDictionary[SessionID][Properties.Settings.Default.NextStage] = "Subject";

            while(!Global.SessionVariables[SessionID][Properties.Settings.Default.SubjectCount].Lock("0"));
            Global.SessionVariables[SessionID][Properties.Settings.Default.SubjectCount].Value = (int.Parse(Global.SessionVariables[SessionID][Properties.Settings.Default.SubjectCount].Value) + 1).ToString();
            Global.SessionVariables[SessionID][Properties.Settings.Default.SubjectList].Value = Global.SessionVariables[SessionID][Properties.Settings.Default.SubjectList].Value.Replace("]", "") + "," + SubjectID + "]";
            if (Global.SessionVariables[SessionID][Properties.Settings.Default.SubjectList].Value[1] == ',')
            {
                Global.SessionVariables[SessionID][Properties.Settings.Default.SubjectList].Value = Global.SessionVariables[SessionID][Properties.Settings.Default.SubjectList].Value.Replace(",", "");
            }
            Global.SessionVariables[SessionID][Properties.Settings.Default.SubjectCount].Release("0");

            Global.SubjectVariables[SubjectID.Replace("\"","")] = SubjectVariables;

            return SubjectID;
        }

        /// <summary>
        /// Occures on every page load (first time or Post Back).
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void Page_Load(object sender, EventArgs e)
        {
            if (Request.QueryString["InvitationCode"] != null && Request.QueryString["SubjectID"] == null)
            {
                int InvitationID = BEELab.Logic.ProgramManagement.SessionManagement.InvitationManagement.GetInvitationIDByCode(Request.QueryString["InvitationCode"].ToString());

                if (InvitationID != 0)
                {
                    string SubjectID = JoinSession(InvitationID);
                    if (SubjectID == null)
                    {
                        Literal1.Text = "ERROR: Session is inactive";
                    }
                    else
                    {
                        SubjectID = SubjectID.Replace("\"", "");
                        foreach (string key in Request.QueryString.Keys)
                        {
                            if (key != "InvitationCode" && key != "SubjectID")
                            {
                                if (Global.SubjectVariables[SubjectID].ContainsKey(key))
                                {
                                    Global.SubjectVariables[SubjectID][key].Value = "\"" + Request.QueryString[key] + "\"";
                                }
                                else
                                {
                                    Global.SubjectVariables[SubjectID][key] = new VariableManagement.Variable(key, key, "Subject", "\"" + Request.QueryString[key] + "\"");
                                }
                            }
                        }
                        string token1 = CreateServerSideExecutionToken(20);

                        if (Request.QueryString["NoHeader"] == null || Request.QueryString["NoHeader"].ToString().ToLower() != "true")
                        {
                            Response.Redirect(Request.RawUrl.Replace(Request.Url.Query, "?SubjectID=" + SubjectID + "&Token1=" + token1));
                        }
                        else
                        {
                            Response.Redirect(Request.RawUrl.Replace(Request.Url.Query, "?SubjectID=" + SubjectID + "&Token1=" + token1 + "&NoHeader=True"));
                        }
                    }
                }
                else
                {
                    Literal1.Text = "ERROR: Bad invitation code";
                    return;
                }
            }

            if (Request.QueryString["SubjectID"] != null)
            {
                string token1 = null;
                if (Request.QueryString["Token1"] != null)
                {
                    token1 = Request.QueryString["Token1"].ToString();
                }
                string token2 = null;
                if (Request.QueryString["Token2"] != null)
                {
                    token2 = Request.QueryString["Token2"].ToString();
                }

                string SubjectID = Request.QueryString["SubjectID"];
                int SessionID = InvitationManagement.GetSessionIDByInvitationID(SessionManagement.GetInvitationIDBySubjectID(SubjectID.Replace("\"", "")));

                if (Global.SubjectVariables.ContainsKey(SubjectID) == false)
                {
                    if (Global.SessionVariables.ContainsKey(SessionID) == false)
                    {
                            Literal1.Text = "Session is inactive";
                            return;
                    }
                    else
                    {
                        Literal1.Text = "Invalid SubjectID";
                        return;                        
                    }
                }

                this.SubjectVariables = Global.SubjectVariables[SubjectID];

                if (Request.QueryString["InvitationCode"] == null)
                {
                    if (token2 != null && Global.Tokens.Contains(token2))
                    {
                        ManagePostedData(SessionID);
                        ServerSideExecution(SubjectID, SessionID, Properties.Settings.Default.AfterPostBlockName + "();", Request.QueryString["Stage"], token2);
                        RecordData(SessionID, SubjectID, Request.QueryString["Stage"]);
                    }
                }

                if (token1 != null && Global.Tokens.Contains(token1))
                {
                    ServerSideExecution(SubjectID, SessionID, Properties.Settings.Default.BeforeLoadBlockName + "();", this.SubjectVariables[Properties.Settings.Default.NextStage].Value.Replace("\"", ""), token1);
                }
                else if (Request.QueryString["Token1"] == null)
                {
                    string token = CreateServerSideExecutionToken(20);
                    ServerSideExecution(SubjectID, SessionID, Properties.Settings.Default.BeforeLoadBlockName + "();", this.SubjectVariables[Properties.Settings.Default.NextStage].Value.Replace("\"", ""), token);
                }
                Literal1.Text = GenerateStage(SubjectID, SessionID, false, BEELab.Properties.Settings.Default.OnLoadBlockName + "();", this.SubjectVariables[Properties.Settings.Default.NextStage].Value.Replace("\"", ""));

                Global.LastSessionActivity[SessionID] = DateTime.Now;
            }
        }

        /// <summary>
        /// Adds new error to page errors list
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Page_Error(object sender, EventArgs e)
        {
            if (Session["Errors"] == null)
            {
                Session["Errors"] = 0;
            }

            Session["Errors"] = int.Parse(Session["Errors"].ToString()) + 1;

            if (int.Parse(Session["Errors"].ToString()) <= 3)
            {
                RecordData(Request.QueryString["SubjectID"].ToString(), "Page_Error", Data.CryptographyHelper.GenerateRandomString(5), Server.GetLastError().Message);

                Response.Redirect(Request.RawUrl);
                Server.ClearError();
            }
        }

        /// <summary>
        /// Generates random string in requested length.
        /// </summary>
        /// <param name="length">String length</param>
        /// <returns>Random string</returns>
        public string CreateServerSideExecutionToken(int length)
        {
            string res = Data.CryptographyHelper.GenerateRandomString(length);
            Global.Tokens.Add(res);

            return res;
        }

        /// <summary>
        /// Executes new operation from server side securely.
        /// </summary>
        /// <param name="SubjectID">SubjectID</param>
        /// <param name="SessionID">SessionID</param>
        /// <param name="command">Command</param>
        /// <param name="StageName">Stage Name</param>
        /// <param name="token">Token</param>
        private void ServerSideExecution(string SubjectID, int SessionID, string command, string StageName, string token)
        {
            if (Global.Tokens.Contains(token) == false)
            {
                return;
            }

            var engine = new V8ScriptEngine();

            string code = GenerateStage(SubjectID, SessionID, true, command, StageName);
            try
            {
                System.Threading.TimerCallback tcb = this.InterrupServerSideExecution;
                System.Threading.Timer timer = new System.Threading.Timer(tcb, engine, 30000, System.Threading.Timeout.Infinite);

                engine.AddHostType("HTTP", typeof(BEELab.Data.HTTPHelper));
                engine.Execute("delete ActiveXObject");
                engine.Execute(code);

                timer.Dispose();
            }
            catch (Exception ex)
            {
                RecordData(SubjectID, StageName, "ServerSideException_" + command, ex.Message);

                engine.Dispose();

                Global.Tokens.Remove(token);

                return;
            }

            try
            {
                foreach (VariableManagement.Variable variable in Global.SessionVariables[SessionID].Values)
                {
                    engine.Execute(variable.Name + " = Stringify(" + variable.Name + ");");
                    string newValue = engine.Evaluate(variable.Name).ToString();
                    string oldValue = engine.Evaluate("_" + variable.Name).ToString();

                    if (oldValue != newValue)
                    {
                        variable.mutex.WaitOne();
                        if (variable.isLocked == false || variable.lockOwner == SubjectID)
                        {
                            variable.Value = newValue;
                        }
                        variable.mutex.ReleaseMutex();
                    }
                }
                foreach (VariableManagement.Variable variable in this.SubjectVariables.Values)
                {
                    engine.Execute(variable.Name + " = Stringify(" + variable.Name + ");");
                    string newValue = engine.Evaluate(variable.Name).ToString();
                    string oldValue = engine.Evaluate("_" + variable.Name).ToString();
                    if (oldValue != newValue)
                    {
                        variable.Value = newValue;
                    }
                }
            }
            catch (Exception ex)
            {
                RecordData(SubjectID, StageName, "ServerSideException_" + command, ex.Message);
            }
            finally
            {
                engine.Dispose();
                Global.Tokens.Remove(token);
            }
            
        }

        public void InterrupServerSideExecution(Object obj)
        {
            RecordData("0", "ServerSideInterrupt", "ServerSideInterrupt", "ServerSideInterrupt");
            try
            {
                ((V8ScriptEngine)obj).Interrupt();
            }
            catch (Exception ex)
            {
                RecordData("0", "ServerSideInterrupt", "ServerSideInterruptException", ex.Message);
            }
            finally
            {
                ((V8ScriptEngine)obj).Dispose();
            }

            //((Microsoft.ClearScript.Windows.JScriptEngine)obj).Interrupt();
            //((Microsoft.ClearScript.Windows.JScriptEngine)obj).Dispose();
        }

        private void RecordData(int SessionID, string SubjectID, string StageName)
        {
            int StageID = StageManagement.GetIDByName(SessionManagement.GetProgramIDBySessionID(SessionID), StageName);
            foreach (DataElements.DataElement element in Global.DataElementsDictionary[SessionID].Values)
            {
                if (element.StageID != StageID)
                    continue;

                try
                {

                    SqlCommand command = new SqlCommand();
                    command.CommandText = "INSERT INTO RecordedData (SubjectID, Label, Value) VALUES (@SubjectID, @Label, @Value)";
                    command.Parameters.Add(new SqlParameter("@SubjectID", SubjectID));
                    command.Parameters.Add(new SqlParameter("@Label", StageName + "_" + HandlePlaceholders(element.Label, SessionID, SubjectID, StageID)));

                    string VariableName = Global.VariableIDNameDictionary[SessionID][element.VariableID];
                    string VariableValue;
                    if (Global.VariableScopeDictionary[SessionID][VariableName] == "Session")
                    {
                        VariableValue = Global.SessionVariables[SessionID][VariableName].Value;
                    }
                    else
                    {
                        VariableValue = this.SubjectVariables[VariableName].Value;
                    }

                    VariableValue = HandlePlaceholders(VariableValue, SessionID, SubjectID, StageID);

                    if (VariableValue.StartsWith("\""))
                    {
                        VariableValue = VariableValue.Substring(1, VariableValue.Length - 2);
                    }

                    command.Parameters.Add(new SqlParameter("@Value", VariableValue));
                    DBHelper.ExecuteNonQuery(command);
                }
                catch (Exception) { }
            }
        }

        public void RecordData(string SubjectID, string Stage, string Label, string Value)
        {
            int SessionID = BEELab.Logic.ProgramManagement.SessionManagement.SessionManagement.GetSessionIDBySubjectID(SubjectID);

            int StageID = BEELab.Logic.ProgramManagement.StageManagement.StageManagement.GetIDByName(SessionManagement.GetProgramIDBySessionID(SessionID), Stage);

            SqlCommand command = new SqlCommand();
            command.CommandText = "INSERT INTO RecordedData (SubjectID, Label, Value) VALUES (@SubjectID, @Label, @Value)";
            command.Parameters.Add(new SqlParameter("@SubjectID", SubjectID));
            command.Parameters.Add(new SqlParameter("@Label", Stage + "_" + Label));
            command.Parameters.Add(new SqlParameter("@Value", Value));
            BEELab.Data.DBHelper.ExecuteNonQuery(command);
        }

        private string TransformDescription(BlockElements.BlockElement Element)
        {
            return string.Concat("//", Element.Description.Replace("\n", "\n //"));
        }

        private string TransformContent(BlockElements.BlockElement Element, int SessionID, bool nested)
        {
            if (Element.Type == "Code")
                return Element.Content;

            if (Element.Type == "Call")
                return Properties.Settings.Default.BlockPrefix + Element.Content + Properties.Settings.Default.BlockSuffix + ";";

            StringBuilder result = new StringBuilder("");

            if (Element.Name == Properties.Settings.Default.OnMessageBlockName)
            {
                result = new StringBuilder("function " + Element.Name  + "(message){" + Environment.NewLine);
            }
            else
            {
                if (nested)
                {
                    return Properties.Settings.Default.BlockPrefix + Element.Name + Properties.Settings.Default.BlockSuffix + ";";
                }
                result = new StringBuilder("function " + Properties.Settings.Default.BlockPrefix + Element.Name + Properties.Settings.Default.BlockSuffix + "{" + Environment.NewLine);
            }

            for (int i = 0; i < Element.Children.Count; i++)
            {
                result.AppendLine(TransformElement(Global.BlockElementsDictionary[SessionID][Element.Children[Element.Children.Keys.ElementAt(i)]], SessionID, true));
            }

            if (Element.Name == Properties.Settings.Default.OnPostBlockName)
            {
                result.Append("HasPosted = true;");
                result.AppendLine("if (PendingMessages == 0) { PostForm(); }");
            }

            result.AppendLine("}");

            return result.ToString();
        }

        private string TransformElement(BlockElements.BlockElement Element, int SessionID, bool nested)
        {
            return string.Concat(TransformDescription(Element), Environment.NewLine, TransformContent(Element, SessionID, nested));
        }

        /// <summary>
        /// Generates existing stage of an experiment. 
        /// </summary>
        /// <param name="SubjectID">Subject ID</param>
        /// <param name="SessionID">Session ID</param>
        /// <param name="isServerSide">Boolean value - if True, operation is executed on server side</param>
        /// <param name="command">Command</param>
        /// <param name="StageName">Stage Name</param>
        /// <returns>Stage code after parsing</returns>
        private string GenerateStage(string SubjectID, int SessionID, bool isServerSide, string command, string StageName)
        {
            int ProgramID = SessionManagement.GetProgramIDBySessionID(SessionID);
            int StageID = StageManagement.GetIDByName(ProgramID, StageName);

            if (StageID == 0)
            {
                return "ERROR: Stage '" + StageName + "' not found";
            }

            StringBuilder StageContent = new StringBuilder();

            if (!isServerSide)
            {
                StageContent.AppendLine("<script>");
            }

            StageContent.AppendLine("//Define Session Variables");
            foreach (VariableManagement.Variable variable in Global.SessionVariables[SessionID].Values)
            {
                StageContent.AppendLine("var " + variable.Name + ";");
            }
            StageContent.AppendLine("//Define Subject Variables");
            foreach (VariableManagement.Variable variable in this.SubjectVariables.Values)
            {
                StageContent.AppendLine("var " + variable.Name + ";");
            }

            StageContent.AppendLine("//Load Session Variables");
            string value;
            foreach (VariableManagement.Variable variable in Global.SessionVariables[SessionID].Values)
            {
                value = variable.Value;
                StageContent.AppendLine(variable.Name + " = " + value + ";");
                StageContent.AppendLine("var _" + variable.Name + " = " + value + ";");
            }

            StageContent.AppendLine("//Load Subject Variables");
            StageContent.AppendLine("var CurrentStage = '" + StageName + "';");
            StageContent.AppendLine("var HasPosted = false;");
            StageContent.AppendLine("var PendingMessages = 0;");

            foreach (VariableManagement.Variable variable in this.SubjectVariables.Values)
            {
                StageContent.AppendLine(variable.Name + " = " + variable.Value + ";");
                StageContent.AppendLine("var _" + variable.Name + " = " + variable.Value + ";");
            }

            StageContent.AppendLine("//Logic Layer Functions");
            foreach (BlockElements.BlockElement element in Global.BlockElementsDictionary[SessionID].Values)
            {
                if (element.Type == "Block" && element.StageID == StageID && !(isServerSide == false && (element.Name == "BeforeLoad" || element.Name == "AfterPost") ))
                {
                    StageContent.AppendLine(TransformElement(element, SessionID, false));
                }
            }

            if (!isServerSide)
            {
                
                StageContent.AppendLine("window.onbeforeunload = desert;");
                StageContent.AppendLine("function desert(){");
                StageContent.AppendLine("    if (!HasPosted) {");
                StageContent.AppendLine("        messageHub.server.desert(SubjectID);");
                //StageContent.AppendLine("        if (typeof(OnDesert) == 'function') { OnDesert(); }");
                StageContent.AppendLine("    }");
                StageContent.AppendLine("}");
                

                StageContent.AppendLine("var messageHub = $.connection.messageHub;");
                StageContent.AppendLine("$(function () {");
                StageContent.AppendLine("   messageHub.client.acceptMessage = function (recipient, stage, message, messageID) {");
                StageContent.AppendLine("        if (recipient == SubjectID && stage == CurrentStage && !HasPosted) {");
                StageContent.AppendLine("            PendingMessages++;");
                StageContent.AppendLine("            messageHub.server.confirmDelivery(SubjectID, '" + StageName + "', messageID).done(function(res){ PendingMessages--; if (PendingMessages == 0 && HasPosted) { PostForm(); } }).fail(function(){ setTimeout(function(){ PendingMessages--; messageHub.client.acceptMessage(recipient, stage, message, messageID); }, 3000);});");
                StageContent.AppendLine("            OnMessage(message);");
                StageContent.AppendLine("        }");
                StageContent.AppendLine("    };");

                StageContent.AppendLine("   messageHub.client.asyncSetVariable = function (recipient, varName, varValue) {");
                StageContent.AppendLine("        if (HasPosted) { return; }");
                StageContent.AppendLine("        eval(varName + ' = ' + varValue + ';');");
                StageContent.AppendLine("    };");

                StageContent.AppendLine("   messageHub.client.setVariableAndInvoke = function (recipient, varName, varValue, func) {");
                StageContent.AppendLine("        if (HasPosted) { return; }");
                StageContent.AppendLine("        eval(varName + ' = ' + varValue + ';');");
                StageContent.AppendLine("        window[func]();");
                StageContent.AppendLine("    };");

                StageContent.AppendLine("});");


                StageContent.AppendLine("function Lock(VarName, Callback) {");
                StageContent.AppendLine("    return messageHub.server.lock(SubjectID,   VarName, Callback).done(function(res){ return res; }).fail(function(){ setTimeout(function(){ Lock(VarName, Callback); }, 3000); });");
                StageContent.AppendLine("}");

                StageContent.AppendLine("function Release(VarName, Callback) {");
                StageContent.AppendLine("    messageHub.server.release(SubjectID, VarName, Callback).fail(function(){ setTimeout(function(){ Release(VarName, Callback); }, 3000); });");
                StageContent.AppendLine("}");

                StageContent.AppendLine("function SendMessage(recipient, message) {");
                StageContent.AppendLine("    PendingMessages++;");
                StageContent.AppendLine("    messageHub.server.send('" + StageName + "', recipient, message).done(function(res){ PendingMessages--; if (PendingMessages == 0 && HasPosted) { PostForm(); } }).fail(function(){PendingMessages--; setTimeout(function(){ SendMessage(recipient, message); }, 3000); });");
                StageContent.AppendLine("}");

                StageContent.AppendLine("function SendMessageToAll(message) {");
                StageContent.AppendLine("    PendingMessages++;");
                StageContent.AppendLine("    messageHub.server.sendToAll(SubjectID, '" + StageName + "', SubjectID, message).done(function(res){ PendingMessages--; if (PendingMessages == 0 && HasPosted) { PostForm(); } }).fail(function(){PendingMessages--; setTimeout(function(){ SendMessageToAll(message); }, 3000); });");
                StageContent.AppendLine("}");

                StageContent.AppendLine("function RecordData(Label, Value) {");
                StageContent.AppendLine("    PendingMessages++;");
                StageContent.AppendLine("    return messageHub.server.recordData(SubjectID, '" + StageName + "', Label, Value).done(function(res){ PendingMessages--; if (PendingMessages == 0 && HasPosted) { PostForm(); } }).fail(function(){PendingMessages--; setTimeout(function(){ RecordData(Label, Value); }, 3000); });");
                StageContent.AppendLine("}");

                StageContent.AppendLine("function GetValue(VariableName, Callback) {");
                StageContent.AppendLine("     messageHub.server.getValue(SubjectID, VariableName, Callback).fail(function(){ setTimeout(function(){ GetValue(VariableName, Callback); }, 3000); });");
                StageContent.AppendLine("}");

                StageContent.AppendLine("function SetValue(VariableName, Value, Callback) {");
                StageContent.AppendLine("    messageHub.server.setValue(SubjectID, VariableName, Stringify(Value), Callback).fail(function(){ setTimeout(function(){ SetValue(VariableName, Value, Callback); }, 3000); });");
                StageContent.AppendLine("}");

                StageContent.AppendLine("function SetVariable(varName, varValue){");
                StageContent.AppendLine("    window[varName] = varValue; }");

                StageContent.AppendLine("var hasInitiated = 0;");

                StageContent.AppendLine("$( document ).ready(function() { $.connection.hub.start().done(function () {");
                StageContent.AppendLine("messageHub.server.register('" + SubjectID + "', '" + StageName + "').done(function(){");
                StageContent.AppendLine("if(hasInitiated == 0){");
                StageContent.AppendLine(command);
                StageContent.AppendLine("hasInitiated = 1;");
                StageContent.AppendLine("}");
                StageContent.AppendLine("messageHub.server.getMessages(SubjectID, '" + StageName + "');");
                StageContent.AppendLine("});");
                StageContent.AppendLine("$(window).keydown(function(event){ if(event.keyCode == 13) { event.preventDefault(); return false; } });");
                
                StageContent.AppendLine("$('#wrap').show();");
                StageContent.AppendLine("}); });");

                StageContent.AppendLine("$.connection.hub.disconnected(function() { $.connection.hub.start().done(function () {");
                StageContent.AppendLine("    if (HasPosted) { return; }");
                StageContent.AppendLine("    messageHub.server.register('" + SubjectID + "', '" + StageName + "');");
                StageContent.AppendLine("if(hasInitiated == 0){");
                StageContent.AppendLine(command);
                StageContent.AppendLine("hasInitiated = 1;");
                StageContent.AppendLine("}");
                StageContent.AppendLine("    messageHub.server.getMessages(SubjectID, '" + StageName + "');");
                StageContent.AppendLine("});});");

                StageContent.AppendLine("$.connection.hub.reconnected(function() { ");
                StageContent.AppendLine("    messageHub.server.register('" + SubjectID + "', '" + StageName + "');");
                StageContent.AppendLine("    messageHub.server.getMessages(SubjectID, '" + StageName + "');");
                StageContent.AppendLine("}); ");

                StageContent.AppendLine("$(function () {");
                StageContent.AppendLine("   messageHub.client.invoke = function (func) {");
                StageContent.AppendLine("        if (HasPosted) { return; }");
                StageContent.AppendLine("         window[func]();");
                StageContent.AppendLine("        }");
                StageContent.AppendLine("    });");

                StageContent.AppendLine(GeneratePostJSCode(SessionID, SubjectID));

                StageContent.AppendLine("</script>");
            }
            else
            {
                StageContent.AppendLine(command);
            }

            if (!isServerSide)
            {
                StageContent.AppendLine("<script>");
            }

            StageContent.AppendLine("function UndoChanges(varName){");
            StageContent.AppendLine("    window[varName] = window['_' + varName]; }");

            StageContent.AppendLine("function Stringify(Value){");
            StageContent.AppendLine("    return JSON.stringify(Value);");
            StageContent.AppendLine("}");

            if (!isServerSide)
            {
                StageContent.AppendLine("</script>");
            }

            if (!isServerSide)
            {
                StageContent.AppendLine("<script>");
                StageContent.AppendLine("window.location.hash=\"no-back-button\";");
                StageContent.AppendLine("window.location.hash=\"Again-No-back-button\";");
                StageContent.AppendLine("window.onhashchange=function(){window.location.hash=\"no-back-button\";}");
                StageContent.AppendLine("</script>");

                StageContent.AppendLine("<!--Presentation Layer Placeholder-->");
                StageContent.AppendLine("<div id=\"wrap\" class=\"clearfix\" style=\"display: none\">");
                StageContent.AppendLine(Properties.Settings.Default.PresentationPrefix + Properties.Settings.Default.PresentationLayer + StageID + Properties.Settings.Default.PresentationSuffix);
                StageContent.AppendLine("</div>");
            }

            return HandlePlaceholders(StageContent.ToString(), SessionID, SubjectID, StageManagement.GetIDByName(ProgramID, StageName));
        }

        /// <summary>
        /// Replacing all place holders of a specific content with their real values from DB,
        /// according to relevant sessionID, StageID and subjectID.
        /// </summary>
        /// <param name="Content">Content</param>
        /// <param name="SessionID">Session ID</param>
        /// <param name="SubjectID">Subject ID</param>
        /// <param name="stageID">Stage ID</param>
        /// <returns>Content string with real values</returns>
        private string HandlePlaceholders(string Content, int SessionID, string SubjectID, int stageID)
        {
            StringBuilder result = new StringBuilder(Content);

            Boolean flag = true;
            string str;
            int i = 0;
            while (flag && i < 1000)
            {
                flag = false;
                i++;
                str = result.ToString();

                string VariableValue;
                foreach (VariableManagement.Variable variable in Global.SessionVariables[SessionID].Values)
                {
                    VariableValue = variable.Value;

                    if (str.Contains(Properties.Settings.Default.VariablePrefix + variable.Name + Properties.Settings.Default.VariableSuffix))
                    {
                        flag = true;
                        if (VariableValue.StartsWith("\""))
                        {
                            VariableValue = VariableValue.Substring(1, VariableValue.Length - 2);
                        }
                        result.Replace(Properties.Settings.Default.VariablePrefix + variable.Name + Properties.Settings.Default.VariableSuffix, VariableValue);
                    }

                    if (str.Contains(Properties.Settings.Default.VariableNamePrefix + variable.Name + Properties.Settings.Default.VariableNameSuffix))
                    {
                        flag = true;
                        result.Replace(Properties.Settings.Default.VariableNamePrefix + variable.Name + Properties.Settings.Default.VariableNameSuffix, variable.Name);
                    }
                }

                foreach (VariableManagement.Variable variable in this.SubjectVariables.Values)
                {
                    if (str.Contains(Properties.Settings.Default.VariablePrefix + variable.Name + Properties.Settings.Default.VariableSuffix))
                    {
                        flag = true;
                        VariableValue = variable.Value;
                        if (VariableValue.StartsWith("\""))
                        {
                            VariableValue = VariableValue.Substring(1, VariableValue.Length - 2);
                        }
                        result.Replace(Properties.Settings.Default.VariablePrefix + variable.Name + Properties.Settings.Default.VariableSuffix, VariableValue);
                    }
                }

                if (Global.PresentationElementsDictionary[SessionID] != null)
                {
                    foreach (PresentationElements.PresentationElement element in Global.PresentationElementsDictionary[SessionID].Values)
                    {
                        if (str.Contains(Properties.Settings.Default.PresentationPrefix + element.Name + Properties.Settings.Default.PresentationSuffix))
                        {
                            flag = true;
                            result.Replace(Properties.Settings.Default.PresentationPrefix + element.Name + Properties.Settings.Default.PresentationSuffix, element.Content);
                        }
                    }
                }



                if (Global.BlockElementsDictionary[SessionID] != null)
                {
                    foreach (BlockElements.BlockElement element in Global.BlockElementsDictionary[SessionID].Values.Where(elem => elem.StageID == stageID))
                    {
                        if (element.Type == "Block")
                        {
                            if (str.Contains(Properties.Settings.Default.BlockPrefix + element.Name + Properties.Settings.Default.BlockSuffix))
                            {
                                flag = true;
                                result.Replace(Properties.Settings.Default.BlockPrefix + element.Name + Properties.Settings.Default.BlockSuffix, element.Name + "()");
                            }

                            if (str.Contains(Properties.Settings.Default.BlockPrefix + element.Name + Properties.Settings.Default.VariableSuffix))
                            {
                                flag = true;
                                result.Replace(Properties.Settings.Default.BlockPrefix + element.Name + Properties.Settings.Default.VariableSuffix, element.Name);
                            }
                        }
                    }
                }
            }

            return result.ToString();
        }

        /// <summary>
        /// Gets Session ID and Subject ID and returns relevant session JS code after parsing
        /// </summary>
        /// <param name="SessionID">Session ID</param>
        /// <param name="SubjectID">Subject ID</param>
        /// <returns>JS code after parsing</returns>
        private string GeneratePostJSCode(int SessionID, string SubjectID)
        {
            string token1 = CreateServerSideExecutionToken(20);
            string token2 = CreateServerSideExecutionToken(20);

            StringBuilder result = new StringBuilder();
            result.AppendLine("function PostForm(){");
            result.AppendLine("var form = document.createElement(\"form\");");
            result.AppendLine("form.setAttribute(\"method\", \"POST\");");
            if (Request.QueryString["NoHeader"] == null || Request.QueryString["NoHeader"].ToString().ToLower() != "true")
            {
                result.AppendLine("form.setAttribute(\"action\", \"" + "Execute.aspx" + "?SubjectID=" + SubjectID.Replace("\"", "") + "&Stage=" + this.SubjectVariables[Properties.Settings.Default.NextStage].Value.Replace("\"", "") + "&Token1=" + token1 + "&Token2=" + token2 + "\");");
            }
            else
            {
                result.AppendLine("form.setAttribute(\"action\", \"" + "Execute.aspx" + "?SubjectID=" + SubjectID.Replace("\"", "") + "&Stage=" + this.SubjectVariables[Properties.Settings.Default.NextStage].Value.Replace("\"", "") + "&Token1=" + token1 + "&Token2=" + token2 + "&NoHeader=True" + "\");");
            }
            result.AppendLine("var hiddenField;");
            
            foreach (VariableManagement.Variable variable in Global.SessionVariables[SessionID].Values)
            {
                result.AppendLine("if(Stringify(" + variable.Name + ") != Stringify(_" + variable.Name + ")){");

                result.AppendLine("hiddenField = document.createElement(\"input\");");
                result.AppendLine("hiddenField.setAttribute(\"type\", \"hidden\");");
                result.AppendLine("hiddenField.setAttribute(\"name\", \"" + variable.Name + "\");");
                result.AppendLine("hiddenField.setAttribute(\"value\", Stringify(" + variable.Name + "));");
                result.AppendLine("form.appendChild(hiddenField);");

                result.AppendLine("hiddenField = document.createElement(\"input\");");
                result.AppendLine("hiddenField.setAttribute(\"type\", \"hidden\");");
                result.AppendLine("hiddenField.setAttribute(\"name\", \"_" + variable.Name + "\");");
                result.AppendLine("hiddenField.setAttribute(\"value\", Stringify(_" + variable.Name + "));");
                result.AppendLine("form.appendChild(hiddenField);");

                result.AppendLine("}");
            }
            
            foreach (VariableManagement.Variable variable in this.SubjectVariables.Values)
            {
                result.AppendLine("if(Stringify(" + variable.Name + ") != Stringify(_" + variable.Name + ")){");

                result.AppendLine("hiddenField = document.createElement(\"input\");");
                result.AppendLine("hiddenField.setAttribute(\"type\", \"hidden\");");
                result.AppendLine("hiddenField.setAttribute(\"name\", \"" + variable.Name + "\");");
                result.AppendLine("hiddenField.setAttribute(\"value\", Stringify(" + variable.Name + "));");
                result.AppendLine("form.appendChild(hiddenField);");

                result.AppendLine("hiddenField = document.createElement(\"input\");");
                result.AppendLine("hiddenField.setAttribute(\"type\", \"hidden\");");
                result.AppendLine("hiddenField.setAttribute(\"name\", \"_" + variable.Name + "\");");
                result.AppendLine("hiddenField.setAttribute(\"value\", Stringify(_" + variable.Name + "));");
                result.AppendLine("form.appendChild(hiddenField);");

                result.AppendLine("}");
            }
            result.AppendLine("document.body.appendChild(form);");
            result.AppendLine("form.submit();");
            result.AppendLine("}");

            return result.ToString();
        }

        /// <summary>
        /// Manage post data of a session
        /// </summary>
        /// <param name="SessionID">Session ID</param>
        private void ManagePostedData(int SessionID)
        {
            foreach (string key in Request.Form.Keys)
            {
                if (key == Properties.Settings.Default.SubjectList || key == Properties.Settings.Default.SubjectCount || key == Properties.Settings.Default.SubjectID || key.StartsWith("_") == true || Request.Form[key] == Request.Form["_" + key])
                {
                    continue;
                }

                if (Global.VariableScopeDictionary[SessionID].ContainsKey(key))
                {
                    if (Global.VariableScopeDictionary[SessionID][key] == "Session")
                    {
                        Global.SessionVariables[SessionID][key].mutex.WaitOne();
                        if (Global.SessionVariables[SessionID][key].isLocked == false || Global.SessionVariables[SessionID][key].lockOwner == Request.QueryString["SubjectID"].ToString())
                        {
                            Global.SessionVariables[SessionID][key].Value = Request.Form[key];
                        }
                        Global.SessionVariables[SessionID][key].mutex.ReleaseMutex();
                    }
                    else
                    {
                        this.SubjectVariables[key].Value = Request.Form[key];
                    }
                }
            }
        }

    }
}